利用 Performance 面板紀錄效能後,會發現其中以主線程 Main 圖表涵蓋了最多資訊,從開始解析 HTML 到最後繪製出頁面,瀏覽器都做了哪些事情呢?
使用者載入網頁後,瀏覽器經過轉譯、繪製才會顯示內容,大致可分為以下流程:
載入 HTML 後,瀏覽器會開始進行解析,先將原始的 Response 內容轉換為字串,再利用 Tag 建立 Tokens、Nodes,最終建立 Document Object Model(DOM):
而解析 HTML 的過程中若遇到 Inline CSS 或是 <link>
載入 CSS 檔,就會經過類似的流程建立出 CSS Object Model(CSSOM):
接著會利用 CSSOM 來算出 DOM 中每一個 Node 的 ComputedStyle,每個 ComputedStyle 都是包含所有 Style property、value 的超大物件,這個過程就是 Recalculate style。
在 Elements 面板 Inspect 一個 Node 時點開 Computed 分頁就會看到該 Node 的 ComputedStyle,另外也可以透過 JavaScript 取得:getComputedStyle(element)
。
Layout 階段會把 DOM 轉換為 Layout tree,結構和 DOM 非常像,但不可見的 <script>
、<link>
或是加上了 display: none;
的 Node 會被排除,而不在 DOM 中的偽元素 ::before
、::after
會被加入,建立 Layout tree 的結構(此結構常被稱為 Render tree)。
Layout tree 中每個節點叫作 LayoutObject
接著會遍歷 Layout tree 來計算所有 LayoutObject 的位置和大小,根據 LayoutObject 的不同如 LayoutBlock、LayoutInline 等等會有不同的計算方式,其他如字體、Overflow、Scrollbar、float、table、flex 排版、螢幕大小等等位置計算都在此階段完成。
此外在開始 Paint 之前若 LayoutObject 有特定的 Style property 如 transform
、will-change
還會另外建立 PaintLayer,而產生 Layer 的過程就是 Update layer tree。
Paint 可分為 Paint 和 Raster 兩個階段。
第一階段並不會真的進行繪製動作,而是先把 Layout tree 轉換為一連串的繪製步驟,其概念和 Canvas 非常像,例如「在 (x, y) 座標畫一個長寬為 (w, h) 的紅色長方形」,而一個繪製步驟稱為一個 DisplayItem。
雖然所有 LayoutObject 的位置、大小、樣式都確定了,但因為 Stacking order 的關係(position
、z-index
等等影響元素覆蓋關係的 Style),無法遍歷一次 Layout tree 就建立所有 DisplayItem,因此這個階段會依據 Stacking order 多次遍歷 Layout tree 來產生 DisplayItem 陣列(DisplayItemList)。
就結果而言可以想像為將每個 LayoutObejct 轉換為 DisplayItem 後再以 Stacking order 排序,順序規則可以參考 Stacking context。
第二階段就是依據 Update layer tree 建立的 PaintLayer 和所有繪製步驟(DisplayItemList)繪製出多個 Layer,每個 Layer 都是一張點陣圖:
RRGGBBAA
整個 Paint 階段繪製出多個 Layer 後,經由合成器(Compositor)把所有的 Layer 依照順序合併為一張圖,再交由瀏覽器顯示出來。
瀏覽器將多個 Layer 合併為最終結果
順過一次流程後會發現中間還穿插了幾個步驟,因此更完整的 Rendering 流程應該如下:
Performance 面板中也能看到這幾個階段
瀏覽器為了提升繪製頁面的效能,會盡可能的利用上次繪製的結果,而 Layer 就是用來降低觸發 Layout、Paint 階段的次數,舉一些實際的例子,將部分內容移到額外的 Layer 後,就能對整個 Layer 加入動畫且不需要重繪該 Layer 的內容。
Paint 依據 PaintLayer 繪製出所有 Layer 後會進入 Raster 階段算出每個 Layer 的每個像素的顏色,但一個 Layer 可能非常大,不會全部都在視野中,為了加快 Rasterization 的速度,開始前會將 Layer 分為多個區塊(Tile)同時進行 Rasterization。
Raster、Tiling、Composite 階段都會在其他線程中進行(稱為 Impl thread),不會佔用主線程的資源,因此可以發現一個有趣的現象:就算主線程被佔滿,點擊、輸入等操作都沒有反應,頁面還是能夠 Scroll。
有三個 Rasterizer Thread 同時進行
Life of a pixel
https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp